概述
ViewModel 我们在 MVC、MVP 和 MVVM 架构中经常见到这个概念。
本文来介绍 LifeCycle 库中的 ViewModel 库。
ViewModel 库是用来保存应用UI数据的类,它可以在配置变更(比如屏幕选择导致的Activit重建)后继续存在,可以避免因数据Activit重建需要数据保存和恢复问题而导致的各种错误。
因此,Android 推荐使用这样的架构设计,将应用中所有的UI数据保存在ViewModel中,而不是Activity中,这样能保证数据不受比如configuration change 带来的Activity重建而带来的影响。
Android 开发中一个常见的坑是很多的变量、逻辑和数据放在Activity和Fragment中,这样的代码比较混乱和难以维护,这种开发模式违反了单一职责的原则,ViewModel可以有效地划分责任,具体地,它可以用来保存 Activity 的所有UI数据,然后Activity仅负责了解如何在屏幕上显示该数据和接受用户互动,但是它不会处理这些互动,如果应用要加载和存储数据,建议创建一个 Repository 的存储区类。
另外还应该确保 ViewModel 不会因为承担过多的责任而变的臃肿,要避免这种情况,可以创建 Presenter 类,或者实现一种更成熟的架构。
使用
要创建一个 ViewModel,首先要扩展ViewModel类,然后将Activity中之前与UI相关的实例变量摆放在这个ViewModel中,然后在 Activity 中的 onCreate 中从 ViewModel Provider 的框架实用类再获取 ViewModel。
请注意 ViewModel Provider 将获取一个 Activity 的实例,它可以确保Activity重建后获取一个新的Activity实例,不过,请确保它始终和同一个ViewModel关联。
对于ViewModel实例,你可以通过getter函数在Activity中直接获取数据。
ViewModel可以单独使用,当然可以和 LiveData 相结合,ViewModel 返回一个 LiveData 对象,然后在 Activity中创建监听,然后更新数据。
使用 ViewModel 和 LiveData 可以创建反应式界面。也就是说当底层数据更新时,UI界面会相应自动更新。
当然还可以和 Data Binding 结合使用。
1 | public class LiveDataTimerViewModel extends ViewModel { |
1 | private LiveDataTimerViewModel mLiveDataTimerViewModel; |
ViewModel的默认构造函数是没有任何参数的,如果你想要修改,可以使用ViewModelFactory类创建一个自定义的构造函数,
ViewModel 的生命周期
ViewModel 的生命周期是由传递给 ViewModelProviders.of(this)
的生命周期决定的,ViewModel 初始化后保留在内存中,直到 Activity 永久消失,比如退出并销毁。
下图展示了 ViewModel 的生命周期:
在系统第一次调用 Activity 的 onCreate() 方法时初始化 ViewModel,系统可能会在Activity的整个生命周期内多次调用 onCreate(),例如当设备屏幕旋转时。 ViewModel 的生命周期从第一次请求 ViewModel 开始,直到 Activity 被 FINISHED 并销毁。
Fragment 之间共享数据
我们经常遇到在一个Activity中有多个 Fragment 需要互相通信的情况,一般的做法可能就是两个 Fragment 都需要定义一些接口,而它们的 Activity 必须将两者联系在一起。 此外,Fragment 必须处理另一个 Fragment 尚未创建或可见的情况。
使用 ViewModel 可以解决这个问题, Fragment 可以通过一个在 Activity 范围内共享的 ViewModel 来处理彼此的通信。
1 | public class SharedViewModel extends ViewModel { |
请注意,在获取ViewModel时,两个Fragment都要使用getActivity()。这样,这两个Fragment会收到同一个SharedViewModel实例,该实例的作用域为Activity。
这样做有以下好处:
- 这个 Activity 不需要做任何事情,也不需要知道 Fragment 之间的交流。
- 除了 SharedViewModel 的接口之外,Fragment不需要了解彼此。 如果其中一个 Fragment 消失,另一个 Fragment 继续照常工作。
- 每个 Fragment 都有自己的生命周期,不受其他生命周期的影响。 一个 Fragment 替换成另一个 Fragment,UI继续工作也没有任何问题。
用 ViewModel 来代替 Loaders
在之前我们会经常使用 CursorLoader 来保持应用程序中的数据与数据库同步。那么现在就可以用 ViewModel 来代替 Loader 的使用了。使用 ViewModel 可以将 UI controller 与数据加载操作分开,这意味着在类之间的强引用减少了。
Loader 通用的做法是使用 CursorLoader 来观察数据库中的数据,当数据库的数据变化时,Loader 会自动触发一次数据加载并更新UI,如下图:
现在我们可以使用 ViewModel + LiveData + Room 的组合来代替 Loader,ViewModel 确保设备配置改变后数据仍然存在,当数据发生变化时,Room 会通知 LiveData,而 LiveData 可以来更新 UI。如下图:
注意事项
不要将 Context 传入 ViewModel
也就是说 Activity、Fragment和View都不能被传入,正如之前介绍,ViewModel 可以比相联的 Activity 和Fragment 的生命周期都要长。假设你在 ViewModel 中存储了一个Activity,当旋转屏幕导致Activity销毁时,但是 ViewModel 还保存着已经被销毁的 Activity 的引用,这样就会造成内存泄漏。
如果你需要比ViewModel生命周期长的Application类,可以使用 AndroidViewModel的子类,通过这个子类就可以直接使用 Application 的引用了。
ViewModel 和 onSaveInstanceState 并用
ViewModel 不应该取代 onSaveInstanceState 的使用,他们两者是相辅相成的。当进程被关闭时,ViewModel将被销毁,但是 onSaveInstanceState 将不会受到影响。
另外,ViewModel可以用来存储大量数据,而 onSaveInstanceState 就只可以用来存储有限的数据了。我们尽可能把多一点的UI数据存储到ViewModel中。在Activity重新创建是不需要重新加载或者生成数据。
如果进程被关闭,我们应该用 onSaveInstanceState 来存储足够还原 UI 状态的最少数据。
源码分析
照例来看看源码实现。
创建实例
ViewModelProviders.of(this).get(TimerViewModel.class)
这个方法会返回一个 TimerViewModel
实例对象。
先来看一下 ViewModelProvider 的 get 方法:
1 | public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { |
ViewModelStore 其实就是内部维护了一个 HashMap 来存放 ViewModel 对象的。
ViewModelStore 是在 ViewModelProviders.of 方法中创建 ViewModelProvider
1 | public static ViewModelProvider of(@NonNull FragmentActivity activity, |
1 | public static ViewModelStore of(@NonNull FragmentActivity activity) { |
HolderFragment.holderFragmentFor()
1 | HolderFragment holderFragmentFor(FragmentActivity activity) { |
从上面这系列分析可以看到,ViewModel 生命周期和 ViewModelStore 相关,而 ViewModelStore 又和 HolderFragment 相关。
那么 HolderFragment 是什么呢?接下来分析。
HolderFragment
1 | public class HolderFragment extends Fragment implements ViewModelStoreOwner { |
HolderFragment 继承自 Fragment 实现了 ViewModelStoreOwner 接口,在生命周期结束的 onDestroy() 执行 ViewModelStore.clear(),然后这个方法会调用 ViewModel 的 onCleared() 方法。
那么 HolderFragment 是怎么做到在 Activity 配置变更导致 Activity 重建的情况下而不销毁呢?
玄机就在 setRetainInstance(true)
这里。
我们称之为 Fragment 的非中断保存,设置了这个属性的 Fragment 当它依附的 Activity 由于配置变更导致的重建时,该 Fragment 不会销毁。
ViewModel 维持它的生命周期正式巧用了 Fragment 的这一特性。
总结
综合上面的源码分析,我们可以得到下面的结论:
- ViewModel 的生命周期和 HolderFragment 保持同步。
- HolderFragment 持有 ViewModelStore,ViewModelStore 持有 ViewModel,ViewModel 以键值对缓存在 ViewModelStore 的 HashMap 中。
- 一个 Activity 或者 Fragment 只会有一个HolderFragment。
- 一个 Activity 或者 Fragment 可以有多个 ViewModel。